package com.seleniti.enpareja.web.desktop.server.model.integration.service.impl.appengine;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.ioc.commons.utils.Format;
import org.ioc.commons.utils.logs.Log4JFormatterLogger;

import com.google.android.gcm.server.Message;
import com.google.android.gcm.server.MulticastResult;
import com.google.android.gcm.server.Sender;
import com.seleniti.enpareja.controller.common.exception.EnParejaException;
import com.seleniti.enpareja.controller.model.integration.database.domain.Couple;
import com.seleniti.enpareja.controller.model.integration.database.domain.Notification;
import com.seleniti.enpareja.controller.model.integration.database.domain.NotificationType;
import com.seleniti.enpareja.controller.model.integration.database.domain.User;
import com.seleniti.enpareja.controller.model.integration.gcm.Constants;

public class GAENotificationService extends GAEService {

	private static final String API_KEY = "AIzaSyAXEgfDPVu4O-Ztsq3B1rEWa5kx89DNVQw";

	private static final Log4JFormatterLogger logger = Log4JFormatterLogger.getLogger(GAENotificationService.class);

	private static final int GCM_RETRIES = 5;

	public GAENotificationService() {
		// Default const.
	}

	public GAENotificationService(GAEService from) {
		this.pm = from.pm;
	}

	public void notifyLoggedUserCouple(NotificationType notifType) throws EnParejaException {
		notifyLoggedUserCouple(notifType, new Date());
	}

	public void notifyLoggedUserCouple(NotificationType notifType, String extraInfo) throws EnParejaException {
		notifyLoggedUserCouple(notifType, new Date());
	}

	public void notifyLoggedUser(NotificationType notifType) throws EnParejaException {
		notifyLoggedUser(notifType, new Date());
	}

	public void notifyLoggedUser(NotificationType notifType, String extraInfo) throws EnParejaException {
		notifyLoggedUser(notifType, extraInfo, new Date());
	}

	public void notifyUser(NotificationType notifType, User user, User sender) {
		notifyUser(notifType, user, sender, null);
	}

	public void notifyLoggedUserCouple(NotificationType notifType, Date when) throws EnParejaException {
		notifyLoggedUserCouple(notifType, null, when);
	}

	public void notifyLoggedUserCouple(NotificationType notifType, String extraInfo, Date when)
			throws EnParejaException {
		User lu = getLoggedUser();

		User couple = lu.getCouple();
		if (couple == null) {
			return;
		}

		if (couple.getNotifications() == null) {
			couple.setNotifications(new HashSet<Notification>());
		}

		notifyUserByGcm(notifType, couple, lu, extraInfo, when);
	}

	public void storeLoggedUserScheduledNotification(NotificationType notifType, String extraInfo, Date when,
			String elementId) throws EnParejaException {
		User lu = getLoggedUser();

		if (lu.getNotifications() == null) {
			lu.setNotifications(new HashSet<Notification>());
		}

		Notification n = createNewNotification(notifType, lu, lu, extraInfo, when, elementId);

		pm.makePersistent(n);

		lu.getNotifications().add(n);
	}

	public void notifyLoggedUser(NotificationType notifType, Date when) throws EnParejaException {
		notifyLoggedUser(notifType, null, when);
	}

	public void notifyLoggedUser(NotificationType notifType, String extraInfo, Date when) throws EnParejaException {
		User lu = getLoggedUser();

		if (lu.getNotifications() == null) {
			lu.setNotifications(new HashSet<Notification>());
		}

		notifyUserByGcm(notifType, lu, lu, extraInfo, when);
	}

	public void notifyUser(NotificationType notifType, User user, User sender, Date when) {
		notifyUserByGcm(notifType, user, sender, null, when);
	}

	protected void notifyUserByGcm(NotificationType notifType, User user, User sender, String extraInfo, Date when) {
		notifyUserByGcm(notifType, user, sender, extraInfo, when, null);
	}

	protected void notifyUserByGcm(NotificationType notifType, User user, User sender, String extraInfo, Date when,
			String elementId) {

		if (notifType.isChangeNotification() && user.getSettings() != null
				&& !user.getSettings().isNotifyMeWhenChanges()) {
			/*
			 * User doesn't want to be notified for changes
			 */
			return;
		}

		for (Iterator<Notification> it = user.getNotifications().iterator(); it.hasNext();) {
			Notification nf = it.next();

			if (minutesDiff(nf.getDate(), new Date()) > 15/* min */) {
				it.remove();
			}
		}

		for (Notification nf : user.getNotifications()) {
			if (nf == null) {
				continue;
			}

			/**
			 * We check if we have some other alike notifications and it is set
			 * as unique. We consider if we have an extra info, it can be
			 * different so in that case we don't remove it.
			 */
			if (nf.getType() == notifType && notifType.isUnique() && extraInfo == null) {
				/**
				 * Notification has been already sent and is unique. We return.
				 */
				return;
			}
		}

		Notification n = createNewNotification(notifType, user, sender, extraInfo, when, elementId);

		if (user.getGcmRegisterId() != null && !user.getGcmRegisterId().isEmpty()) {
			/*
			 * Someone succeeded, so we add notification to get be it
			 */
			pm.makePersistent(n);

			user.getNotifications().add(n);

			String notificationId = n.getNotificationId();
			MulticastResult result = sendMessageToDevice(API_KEY, new ArrayList<>(user.getGcmRegisterId()),
					notificationId);
			if (result.getSuccess() == 0) {

				user.getNotifications().remove(n);
				/*
				 * No one succeeded, so we remove notification (nobody is going
				 * to ask for it)
				 */
				pm.deletePersistent(n);

			}
			// } else {
			// throw new
			// EnParejaException(EnParejaException.ErrorCode.NOTIFICATION_DEVICE_NOT_FOUND);

		}
	}

	@SuppressWarnings("incomplete-switch")
	protected Notification createNewNotification(NotificationType notifType, User user, User sender, String extraInfo,
			Date when, String elementId) {
		Notification n = new Notification();

		n.setDate(when);
		n.setRead(null);
		n.setElementId(null);
		n.setType(notifType);
		n.setUser(user);
		n.setElementId(elementId);

		if (extraInfo == null) {
			switch (notifType) {
			case SHOPPING_LIST_CHANGED:
				n.setExtraInfo(Format.substitute("{0} ha hecho cambios en la lista de la compra", sender.getNickName()));
				break;
			case HOME_TASKS_CHANGED:
				n.setExtraInfo(Format.substitute("{0} ha hecho cambios en las tareas de casa", sender.getNickName()));
				break;
			case MEALS_CHANGED:
				n.setExtraInfo(Format.substitute("{0} ha hecho cambios en la planificación de comidas",
						sender.getNickName()));
				break;
			case SHARED_EXPENSES_CHANGED:
				n.setExtraInfo(Format.substitute("{0} ha hecho cambios en los gastos compartidos", sender.getNickName()));
				break;
			case WORK_SHIFTS_CHANGED:
				n.setExtraInfo(Format.substitute("{0} ha hecho cambios en los gastos compartidos", sender.getNickName()));
				break;
			case STORE_PRODUCTS_CHANGED:
				n.setExtraInfo(Format.substitute("{0} ha hecho cambios en las compras pendientes del hogar",
						sender.getNickName()));
				break;
			}
		} else {
			if (extraInfo.contains("{member")) {
				Couple couple = lookForCouple(user);

				int memberNo = couple != null ? user.equals(couple.getMember1()) ? 1 : 2 : 0;

				String member1 = couple != null && couple.getMember1() != null
						&& couple.getMember1().getNickName() != null ? couple.getMember1().getNickName()
						: (memberNo > 0 ? (memberNo == 1 ? "Tú" : "Tu pareja") : "");
				String member2 = couple != null && couple.getMember2() != null
						&& couple.getMember2().getNickName() != null ? couple.getMember2().getNickName()
						: (memberNo > 0 ? (memberNo == 2 ? "Tú" : "Tu pareja") : "");
				extraInfo = extraInfo.replace("{member1}", member1).replace("{member2}", member2);
			}

			n.setExtraInfo(extraInfo);
		}
		return n;
	}

	private MulticastResult sendMessageToDevice(String apiKey, List<String> devices, String notificationId) {
		Sender sender = new Sender(apiKey);
		Message gcmMessage = new Message.Builder().addData(Constants.NOTIFICATION_ID_KEY, notificationId).build();
		MulticastResult result = null;
		try {
			result = sender.send(gcmMessage, devices, GCM_RETRIES);
		} catch (IOException e) {
			logger.error(e, "Unable to send message to device");
		}

		logger.debug("GCM result: {0}", result);

		return result;
	}

	public Notification find(String id) {
		try {
			Notification found = pm.getObjectById(Notification.class, id);

			if (found != null) {
				found.setServerDate(new Date());
			}

			return found;
		} catch (Exception e) {
			logger.warn("Cannot find notificaciont. Ignoring and returning null.");
			return null;
		}
	}

	public Notification read(String id) {
		Notification nf = find(id);

		if (nf != null) {
			nf.setRead(new Date());
		}

		return nf;
	}

	public boolean registerDeviceForLoggedUser(String registrationId) {
		User lu = getLoggedUser();

		if (lu != null) {
			if (lu.getGcmRegisterId() == null) {
				lu.setGcmRegisterId(new HashSet<String>());
			}

			lu.getGcmRegisterId().add(registrationId);

			return true;
		} else {
			return false;
		}
	}

	public void unregisterDevice(String registrationId) {
		User lu = getLoggedUser();

		if (lu.getGcmRegisterId() != null) {
			lu.getGcmRegisterId().remove(registrationId);
		}
	}

	public static int minutesDiff(Date earlierDate, Date laterDate) {
		if (earlierDate == null || laterDate == null)
			return 0;

		return (int) ((laterDate.getTime() / 60000) - (earlierDate.getTime() / 60000));
	}
}
